import { HookNameCanAsyncType, HookNameType, MessagePluginManager, PluginManager } from './plugins';
import {
    ChatStatus,
    HistoryApiType,
    MessageInfo,
    SendApiType,
    SendBeforeItemType,
    SendParams,
    UserInfoApiType,
    MessageEvent,
    OutputStatus,
    ErrorType,
    InitHistoryStatus,
    ReconnectingInfo,
    SendApiResult
} from './type';
import { KWebsocket } from '@material/websocket';
import { getAiUrl } from './util';
import {
    getLastMessageId,
    getLastMessageList,
    transformMessageItem,
    aboveChatTime
} from './history';
import { AiStreamPlugin } from './plugins/stream';
import { getMessageContent } from './dispose';
import { isArray, jsonStringify, setStore, removeStore } from '@material/helpers';
import { isFunction, isObject } from '@tarojs/shared';
import { AiTimeoutPlugin } from './plugins/overtime';
import { AiNewSessionPlugin } from './plugins/session';
import { AiImcPlugin } from './plugins/imc';
type InitWsCallbackType = (
    params: ChatParamsType,
    initHistoryStatus: InitHistoryStatus,
    data: MessageInfo[]
) => void;
export interface ChatConfig {
    openId: string;
    baseUrl: string;
    sign: string;
    expireTime?: number;
    timeout?: number;
    serviceApi?: ServiceApiType;
}

interface MessageOptions extends ChatConfig {
    plugins?: MessagePluginManager[];
}

export type ChatParamsType = {
    userId: string;
    appid: string;
    baseUrl: string;
    token: string;
    openId: string;
    sign: string;
    expireTime: number;
    timeout: number;
};

export type ServiceApiType = {
    userInfoApi: UserInfoApiType;
    historyApi: HistoryApiType;
    sendApi: SendApiType;
};
export class Message {
    pluginManager: PluginManager;
    chatParams: ChatParamsType;
    serviceApi: ServiceApiType;
    hookMap: Map<string, any>;
    handlers: SendBeforeItemType[];
    chatStatus: ChatStatus;
    transformEvent: boolean;
    renderQueue: MessageInfo[];
    startAddQueue: boolean;
    uuid: number;
    outputStatus: OutputStatus;
    ws?: KWebsocket;
    isInit: boolean;
    constructor(options: MessageOptions) {
        const {
            plugins = [],
            baseUrl,
            openId,
            sign,
            expireTime = 24 * 60,
            timeout = 12,
            serviceApi
        } = options;
        this.pluginManager = new PluginManager();
        this.hookMap = new Map();
        this.handlers = [];
        this.renderQueue = [];
        this.chatParams = {
            userId: '',
            appid: '',
            token: '',
            expireTime,
            timeout,
            baseUrl,
            openId,
            sign
        };
        this.serviceApi = { ...serviceApi } as ServiceApiType;

        this.chatStatus = 'ai';
        this.transformEvent = true;
        this.uuid = 0;
        this.startAddQueue = false;
        this.outputStatus = OutputStatus.Empty;
        this.isInit = false;
        const pluginList = [
            new AiTimeoutPlugin({ timeout }),
            new AiNewSessionPlugin(),
            new AiImcPlugin(),
            new AiStreamPlugin(),
            ...plugins
        ];
        pluginList.forEach((plugin: any) => {
            this.pluginManager.registerPlugin(plugin);
        });
    }
    registerApi(api: ServiceApiType) {
        this.serviceApi = { ...this.serviceApi, ...api };
    }

    async initWs(callback: InitWsCallbackType) {
        if (this.isInit) {
            return;
        }
        this.isInit = true;
        const { serviceApi, chatParams } = this;
        removeStore('AI_Message_Last_id');
        const [err, data] = await getLastMessageList(serviceApi.historyApi, {
            userid: chatParams.userId,
            token: chatParams.token
        });
        if (err) {
            this.triggerHook(MessageEvent.Error, {
                type: ErrorType.API,
                name: 'historyApi',
                message: 'serviceApi.historyApi 初始化获取会话历史列表异常',
                data: {
                    request: {
                        userid: chatParams.userId,
                        token: chatParams.token
                    },
                    response: data
                }
            });
            callback?.(chatParams, 'error', []);
            return;
        }
        const firstLoadData: MessageInfo[] = data;
        const isReset = aboveChatTime(firstLoadData, this.chatParams.expireTime);

        if (isReset && firstLoadData) {
            callback?.(this.chatParams, 'reset', firstLoadData);
            return;
        }
        if (isReset && !firstLoadData) {
            callback?.(this.chatParams, 'init', firstLoadData);
            return;
        }
        if (!isReset) {
            const msgId = getLastMessageId(firstLoadData);
            setStore('AI_Message_Last_id', msgId);
            callback?.(this.chatParams, 'session', firstLoadData);
            return;
        }
    }

    async init(callback: InitWsCallbackType) {
        if (!this.serviceApi) {
            throw new Error('请传递必要的接口参数到serviceApi中');
        }
        const [err, data] = await this.serviceApi.userInfoApi({
            openId: this.chatParams.openId,
            sign: process.env.YZF_AI_SIGIN
        });
        if (err) {
            this.triggerHook(MessageEvent.Error, {
                type: ErrorType.API,
                name: 'userInfoApi',
                message: 'serviceApi.userInfoApi 获取用户信息失败',
                data: {
                    request: {
                        openId: this.chatParams.openId,
                        sign: process.env.YZF_AI_SIGIN
                    },
                    response: data
                }
            });
            return;
        }
        this.chatParams = { ...this.chatParams, ...data };
        const wsUrl = getAiUrl(
            this.chatParams.baseUrl,
            this.chatParams.userId,
            this.chatParams.token
        );

        this.ws = new KWebsocket({ url: wsUrl });

        this.ws.on('open', () => void this.initWs(callback));

        this.ws.on('error', (error) => {
            this.triggerHook(MessageEvent.Error, {
                type: ErrorType.WEBSOCLKET,
                message: '安全模型链接出现异常：',
                data: {
                    wsUrl,
                    ...this.chatParams,
                    error
                }
            });
        });
        this.ws.on('close', (res) => {
            this.triggerHook(MessageEvent.Error, {
                type: ErrorType.WEBSOCLKET,
                message: '安全模型链接关闭：',
                data: {
                    wsUrl,
                    ...this.chatParams,
                    info: res
                }
            });
        });

        this.ws.on('message', (info) => {
            const data = transformMessageItem(info);
            this.transformMessage(data);
        });
        this.ws.on('reconnecting', (info: ReconnectingInfo) => {
            this.triggerHook(MessageEvent.Reconnecting, info);
        });
    }
    getConnectStatus() {
        return this.ws && this.ws.isConnected;
    }
    connectWebsocket() {
        return this.ws?.connect();
    }
    destroyWebsocket() {
        this.ws && this.ws.destroy();
    }
    autoSumId() {
        this.uuid = this.uuid + 1;
        return this.uuid;
    }
    clearRenderQueue() {
        this.renderQueue = [];
    }
    setRenderQueue(value: MessageInfo | MessageInfo[]) {
        if (isArray(value)) {
            this.renderQueue.push(...value);
        } else {
            this.renderQueue.push(value);
        }
    }
    stopChat(messgeId: string) {
        return this.send({
            MsgType: 'event',
            Event: 'cancel_answer',
            transformEvent: false,
            MsgID: messgeId,
            ExtInfo: {
                cancel_id: messgeId
            }
        });
    }
    newTopic(options?: Record<string, any>) {
        return this.send({
            MsgType: 'event',
            Event: 'reset_ai_chat',
            transformEvent: false,
            ExtInfo: {
                ...options
            }
        });
    }
    filterRenderQuequ(info: MessageInfo) {
        const isOutputAi =
            info.FromType !== 'user' && info.From === 'ai' && info.chatStatus === 'ai';
        if (isOutputAi && [OutputStatus.OutputLoading])
            if (isOutputAi && info.outputStatus === OutputStatus.OutputEnd && !this.startAddQueue) {
                this.startAddQueue = true;
                return info;
            }
        if (this.startAddQueue && info) {
            this.setRenderQueue(info);
            return;
        }
        return info;
    }
    sendBefore(fulfilled: SendBeforeItemType) {
        if (typeof fulfilled !== 'function') {
            throw new Error('请传递一个函数');
        }
        this.handlers.push(fulfilled);
    }
    async send(params: SendParams) {
        try {
            const chain = [(config: SendParams) => this.dispatchSend(config)];
            const beforeChain: SendBeforeItemType[] = [];
            if (Array.isArray(this.handlers) && this.handlers.length) {
                this.handlers.forEach((fulfilled) => {
                    beforeChain.unshift(fulfilled);
                });
            }
            Array.prototype.unshift.apply(chain, beforeChain);
            let promise: Promise<any> = Promise.resolve({ ...params });
            while (chain.length) {
                promise = promise.then(chain.shift());
            }
            return promise;
        } catch (error) {
            this.triggerHook(MessageEvent.Error, {
                type: ErrorType.SCRIPT,
                message: '发送消息脚本异常',
                data: error
            });
        }
    }

    async dispatchHookSend<T = SendParams>(
        params: T,
        type: HookNameCanAsyncType,
        hookNames?: HookNameType[]
    ) {
        if (isArray(hookNames) && hookNames.length) {
            if (hookNames.includes(`${type}Async`)) {
                return this.pluginManager.executeHook(`${type}Async`, params, this);
            }
            if (hookNames.includes(type)) {
                const newParams = this.pluginManager.executeHook(type, params, this);
                return Promise.resolve(newParams);
            }
        }
        return Promise.resolve(params);
    }

    mergeHookName(params: SendParams = {}, hookNames: HookNameType[] = []): HookNameType[] {
        const newHookNames: HookNameType[] = [];
        if (params?.MsgType === 'event' && params?.Event === 'reset_ai_chat') {
            newHookNames.push('beforeSendAsync');
            return [...newHookNames, ...hookNames];
        }
        return hookNames;
    }

    async dispatchSend(
        params: SendParams,
        hookNames: HookNameType[] = ['beforeSend', 'startSend', 'afterSend']
    ): Promise<[0 | 1, SendApiResult] | undefined> {
        const { userId, token, appid, sign } = { ...this.chatParams };
        const urlExt = `?appname=Callback&k=${token}`;
        this.outputStatus = OutputStatus.InputStart;
        const { filterSend, ...newParams } = params;
        const mergeHookNames = this.mergeHookName(newParams, hookNames);
        let aiParams = await this.dispatchHookSend(newParams, 'beforeSend', mergeHookNames);
        const { chatStatus, transformEvent, ExtInfo: aiExtInfo, ...reset } = aiParams;
        this.chatStatus = chatStatus || this.chatStatus || 'ai';
        this.transformEvent = transformEvent || this.transformEvent || true;
        const extInfo = isObject(aiExtInfo) ? jsonStringify(aiExtInfo) || {} : aiExtInfo;
        const isOk = isFunction(filterSend)
            ? filterSend({
                  ...aiParams,
                  chatStatus: this.chatStatus,
                  transformEvent: this.transformEvent
              })
            : false;
        if (isOk) {
            return;
        }
        aiParams = await this.dispatchHookSend(
            {
                ...aiParams,
                chatStatus: this.chatStatus,
                transformEvent: this.transformEvent,
                ExtInfo: extInfo
            },
            'startSend',
            mergeHookNames
        );

        const [err, data] = await this.serviceApi.sendApi({
            sign,
            userid: userId,
            token,
            urlExt,
            appid,
            from_mini: '1',
            ExtInfo: extInfo,
            ...reset
        });
        if (err) {
            this.triggerHook(MessageEvent.Error, {
                type: ErrorType.API,
                name: 'sendApi',
                message: 'serviceApi.sendApi 获取发送消息异常',
                data: {
                    request: {
                        openId: this.chatParams.openId,
                        ExtInfo: extInfo,
                        ...reset
                    },
                    response: data
                }
            });
        }

        return this.dispatchHookSend<[0 | 1, SendApiResult]>(
            [err, data],
            'afterSend',
            mergeHookNames
        );
    }
    triggerHook(hookName: string, params: any) {
        if (this.hookMap.has(hookName)) {
            const callback = this.hookMap.get(hookName);
            callback?.(params);
        }
    }

    on(hookName: string, callback: any) {
        if (!this.hookMap.has(hookName)) {
            this.hookMap.set(hookName, callback);
        }
    }
    outputTransformStatus(endType: string, info: MessageInfo) {
        const content = getMessageContent(info);
        if (
            !endType &&
            [OutputStatus.InputEnd, OutputStatus.OutputLoading].includes(this.outputStatus)
        ) {
            this.outputStatus = OutputStatus.OutputStart;
            return [OutputStatus.OutputStart, content];
        }
        if (
            !endType &&
            this.outputStatus &&
            [OutputStatus.OutputStart, OutputStatus.OutputPending].includes(this.outputStatus)
        ) {
            this.outputStatus = OutputStatus.OutputPending;
            return [OutputStatus.OutputPending, content];
        }
        if (endType) {
            this.outputStatus = OutputStatus.OutputEnd;
            return [OutputStatus.OutputEnd, content];
        }
        return [OutputStatus.Empty, ''];
    }
    setOutputStatus(status: OutputStatus) {
        this.outputStatus = status;
    }
    transformMessage(info: MessageInfo) {
        if (info.FromType === 'user') {
            this.outputStatus = OutputStatus.InputEnd;
            this.clearRenderQueue();
            this.startAddQueue = false;
        }

        const newInfo = this.pluginManager.executeHook('beforeReceive', info, this);

        if (!newInfo) {
            return;
        }
        this.triggerHook(MessageEvent.Message, {
            ...info,
            chatStatus: this.chatStatus,
            messgeId: info.MsgId || '',
            id: info.MsgId ? info.MsgId : this.autoSumId()
        });
        this.pluginManager.executeHook('afterReceive', info, this);
    }
    destroy() {
        this.pluginManager.executeHook('beforeDestroy', null, this);
    }
}
export { MessageEvent };
